/* sys:rexxc/rx * * Checks through emails on a POP3 host and purges the unwanted ones. * $VER: SpamFryer.rexx 3:9 (2004-8-19) by Simon N Goodwin $ * * »» French version : Herve Dupont - 02/08/2004 * * SpamFryer.text lists changes since November 2003 Aminet release. */ Account. = '' /* resets the array */ count.1 = 0 count.2 = 0 /*** CONFIGURATION BLOCK **/ /* Account details can be in the file SpamFryer.accounts, or here in the following format: */ /* Account.1.1 = "new.mail.org" */ /* IP number e.g. 194.5.6.7 or domain e.g. mail.isp.net */ /* Account.1.2 = "username1" */ /* Your user name - typically your ISP account name */ /* Account.1.3 = "password1" */ /* The login password you use for email - confidential! */ /* Continue for Account.2.1, Account 2.2 and Account.2.3 etc if you have several accounts. */ keepList = 'SpamFryer.keepList' /* The path and name of your file of details of mails you want to keep */ loseList = 'SpamFryer.loseList' /* The path and name of your file of details of mails you want to lose */ accountFile = 'SpamFryer.accounts' /* The file of login details for your POP3 accounts (if not set above) */ logFile = 'T:SpamFryer.log' /* Specifies the log file this program generates */ screenName = '*' /* log window public screen name, '' for no window or '*' for the default */ lineMax = 60 /* The maximum width of subject line characters reported to console */ noDigits = 0 /* Non-zero to lose all mails with digits in their To: address */ /**************************** END OF CONFIGURATION BLOCK ****************************/ OPTIONS results /* Valeur retournée par Arexx lors des retours de fonctions */ PARSE ARG quiet . IF (UPPER(quiet) == "VERBOSE") THEN verbose = 1 ELSE verbose = 0 IF screenName ~='' THEN DO CALL close STDOUT CALL open(STDOUT, "con:48/48/590/150/SpamFryer3 Log -- Cliquer le gadget de fermeture une fois terminé/CLOSE/WAIT/SCREEN " screenName, w) END IF logFile ~='' THEN IF ~open(log,logFile,'A') THEN IF ~open(log,logFile,'W') THEN logFile = '' CALL Check_Accounts keep = 1 /* Symbolic category names for readability */ lose = 2 gTo = 1 gFrom = 2 gReply = 3 gSubject= 4 gEarly = 5 gDating = 6 /* Used for combination test, not in list */ /* load the lose and keep lists */ list.keep = keepList list.lose = loseList listfield. = 0 listpattern. = '' /****************************************************************/ /* Règles encoquillers, or put them in keeplist and loselist files */ /* Let through mails specifically about this program */ CALL keep_rule "Subject: SpamFryer" CALL keep_rule "Subject: Spam Frier" /* Allow various spellings */ CALL keep_rule "Subject: Spam Fryer" /* These three rules catch a lot of spam */ CALL lose_rule "Subject: *SPAM*" /* Premarked by mail server */ CALL lose_rule "To: www." /* Idiot mailing web server */ CALL lose_rule "To: undisclosed-recipients" /* A spam indicator */ /* These all signify a HTML mail with no plain text - i.e. spam */ CALL lose_rule "Early-Ref: " CALL lose_rule "Early-Ref: text/html" /****************************************************************/ DO type = keep TO lose IF list.type ~='' THEN DO IF ~OPEN(list,list.type,'R') THEN DO sigh = list.type 'non trouvé.' SAY sigh IF logFile ~= '' THEN CALL WRITELN(log, sigh) END ELSE DO IF verbose THEN SAY list.type "rules:" iterations = 0 counter = count.type DO WHILE ~EOF(list) iterations = iterations + 1 listerror = 0 CALL learn_rule READLN(list), type IF listerror THEN DO sigh = 'ERREUR dans' list.type 'ligne' iterations 'sautée(s).' SAY sigh IF logFile ~= '' & verbose THEN CALL WRITELN(log, sigh) END END CALL CLOSE(list) IF verbose THEN DO IF counter = 0 THEN moan = list.type 'non chargé.' ELSE moan = list.type 'chargé.' SAY moan IF logFile ~='' THEN CALL WRITELN(log, moan) END END END END IF Account.1.1 = '' THEN DO sigh = 'Vous devez configurer votre hôte et le nom d''utilisateur dans SpamFryer.rexx' SAY sigh IF logFile ~='' THEN CALL writeln(log, sigh) IF OPEN(list,accountFile,'R') THEN DO sigh = '(ou' accountFile 'si vous préférez les conserver dans des fichiers séparés).' SAY sigh IF logFile ~='' THEN CALL writeln(log, sigh) CALL CLOSE(list) END IF logFile ~='' THEN CALL CLOSE(log) EXIT 5 END idx = 1 DO WHILE LENGTH(Account.idx.1) > 0 pophost = Account.idx.1 username = Account.idx.2 password = Account.idx.3 IF password = '' THEN DO /* Interactive password support by John 'Kwah' Smith */ CALL addlib('rexxreqtools.library', 0, -30, 37) /* Implicitly uses reqtools.library */ CALL addlib(LibName, Priority, Offset, Version) IF ~show('L', 'rexxreqtools.library') THEN DO moan = 'la bibliothèque [rexx]reqtools n''a pas trouvée l''entrée pour le mot de passe.' SAY moan IF logFile ~='' THEN DO IF verbose THEN CALL writeln(log,moan) CALL close(log) END EXIT 20 END password = rtgetstring( , ('Entrez le mot de passe pour ' || pophost) ,,, 'rtgs_invisible = >> true',) END SAY '' SAY 'Vérification du nom d''utilisateur « ' || username || ' ».' netsend = 'TCP:'||pophost||'/pop3' CALL open(net,netsend,'A') IF result = 0 THEN DO moan = 'Utilisateur « ' || username || ' » : -ERR non trouvé.' SAY moan IF logFile ~='' THEN DO IF verbose THEN CALL writeln(log,moan) CALL close(log) END EXIT 10 END instring=READLN(net) CALL test_str(instring) CALL writeln(net,'USER '||username||'0d'x) instring=READLN(net) CALL test_str(instring) CALL writeln(net,'PASS '||password||'0d'x) instring=READLN(net) CALL test_str(instring) CALL writeln(net,'STAT'||'0d'x) instring=READLN(net) CALL test_str(instring) info = substr(instring,5,length(instring)-5) parse var info nmess ' ' noct IF nmess = 0 THEN DO SAY 'Il n y a aucun message en attente.' END ELSE DO moan = 'Messages :' nmess ' Octets :' noct SAY moan IF logFile ~= '' & verbose THEN CALL writeln(log, moan) /* Clear counters */ nlose = 0 IF logFile ~='' THEN DO /* Per-account headings requested by Elwood */ CALL writeln(log,'') CALL writech(log,'Compte « ' || username || ' » sur «' pophost '» a ' || nmess) IF nmess = 1 THEN CALL writeln(log,' message.') ELSE CALL writeln(log,' messages.') END /* Process all messages */ DO message = 1 TO nmess CALL writeln(net,'TOP '||message||' 12'||'0d'x) instring = '' msgdate = '' msgfrom = '' msgsubj = '' msgrepl = '' bad = 0 good = 0 dating = 0 datematch = 0 digitsift = 0 field = 0 DO UNTIL (LEFT(instring,2)='.' || '0d'x) instring=READLN(net) IF good & msgdate ~='' & msgfrom ~='' & msgsubj ~='' THEN ITERATE /* Speed up if we know it s not spam */ /* Speed up if no keeplist: IF bad & ~verbose & msgsubj ~='' THEN ITERATE */ inCaps = UPPER(instring) IF LEFT(instring,6)='From: ' THEN DO field = gFrom msgfrom = instring DO i = 1 TO listfield.keep.gFrom IF POS(listpattern.keep.gFrom.i,inCaps)>0 THEN CALL good_one END DO i = 1 TO listfield.lose.gFrom IF POS(listpattern.lose.gFrom.i,inCaps)>0 THEN CALL bad_one END END IF LEFT(instring,4)='To: ' THEN DO IF noDigits THEN DO DO i = 0 to 9 /* Reject mails with any digits in recipient address */ IF POS(i,instring)>0 THEN DO bad = 1 digitsift = digitsift + 1 IF verbose THEN DO SAY ' ' message ' perdu(s) pour cause de chiffres dans l''adresse.' END END END END field = gTo DO i = 1 TO listfield.keep.gTo IF POS(listpattern.keep.gTo.i,inCaps)>0 THEN CALL good_one END DO i = 1 TO listfield.lose.gTo IF POS(listpattern.lose.gTo.i,inCaps)>0 THEN CALL bad_one END END IF LEFT(instring,10)='Reply-To: ' THEN DO field = gReply msgrepl = instring DO i = 1 TO listfield.keep.gReply IF POS(listpattern.keep.gReply.i,inCaps)>0 THEN CALL good_one END DO i = 1 TO listfield.lose.gReply IF POS(listpattern.lose.gReply.i,inCaps)>0 THEN CALL bad_one END END IF LEFT(instring,9)='Subject: ' THEN DO field = gSubject msgsubj = instring DO i = 1 TO listfield.keep.gSubject IF POS(listpattern.keep.gSubject.i,inCaps)>0 THEN CALL good_one END DO i = 1 TO listfield.lose.gSubject IF POS(listpattern.lose.gSubject.i,inCaps)>0 THEN CALL bad_one END END IF LEFT(instring,6)='Date: ' THEN msgdate = right(instring,length(instring)-6) /* Several of the following indicate notorious dating sites */ IF POS(' DATING ',inCaps)>0 THEN dating = dating + 1; IF POS('CREATED BY WOMEN',inCaps)>0 THEN dating = dating + 1; field = gEarly DO i = 1 TO listfield.keep.gEarly IF POS(listpattern.keep.gEarly.i,inCaps)>0 THEN CALL good_one END DO i = 1 TO listfield.lose.gEarly IF POS(listpattern.lose.gEarly.i,inCaps)>0 THEN CALL bad_one END END IF dating>1 THEN DO field = gDating /* Require two references to zap a mail */ CALL bad_one END IF msgfrom = '' THEN msgfrom = msgrepl IF (LENGTH(msgfrom) + LENGTH(msgsubj)) = 0 THEN bad = 1 /* © Elwood */ IF msgfrom = '' THEN msgfrom = 'From: ???????????' IF msgdate = '' THEN msgdate = 'Date: ???????????' IF msgsubj = '' THEN msgsubj = 'No subject' IF length(msgsubj) > lineMax THEN msgsubj = LEFT(msgsubj,lineMax) IF verbose THEN DO SAY '' SAY 'Message '||message||' sur' nmess ': '||msgdate SAY msgfrom SAY msgsubj END IF (good=0) & (bad>0) THEN DO IF ~verbose THEN SAY "Suppression ("|| message ||"/"|| nmess || ")  »»" msgsubj IF logFile ~= '' THEN DO CALL writeln(log,'Message '||message||': '||msgdate) CALL writeln(log,msgfrom) CALL writeln(log,msgsubj) END IF bad THEN DO CALL writeln(net,'DELE '||message||'0d'x) IF verbose THEN DO moan = "$$$$$ Supprimé" SAY moan IF logFile ~= '' THEN CALL writeln(log,moan) END instring=READLN(net) nlose = nlose + 1 CALL test_str(instring) ITERATE message END END IF ~verbose THEN ITERATE message moan = "----- Laissé sur le serveur" SAY moan IF logFile ~='' & verbose THEN DO CALL writeln(log,'Message '||message||': '||msgdate) CALL writeln(log,msgfrom) CALL writeln(log,msgsubj) CALL writeln(log,moan) END END /* Loop for all messages */ IF nmess ~= 0 THEN DO SAY '' other = nmess-nlose moan = nlose "sur" nmess "courrier" IF nmess ~=1 THEN moan = moan || "s" moan = "Supprimé(s) :" moan "pour le compte" username "sur" pophost SAY moan IF logFile ~='' & verbose THEN CALL writeln(log,moan) END END /* Some messages */ CALL writeln(net,'QUIT'||'0d'x) CALL close(net) idx = idx + 1 END /* do while any accounts unchecked */ IF logFile ~='' THEN CALL close(log) EXIT 0 /* good_one keeps track of emails identified by the keeplist */ good_one: IF field = 0 THEN RETURN 0 /* Unlikely error */ good = 1 IF verbose THEN DO SAY '' messsage 'conservé pour' listpattern.keep.field.i listpattern.keep.field.i.1 = listpattern.keep.field.i.1 + 1 END return 1 /* bad_one keeps track of emails identified by the loselist */ bad_one: IF field = 0 THEN RETURN 0 /* Error */ bad = 1 IF verbose THEN DO IF field = gDating THEN DO SAY '' message 'perdu pour mauvaise référence de datation' datematch = datematch + 1 END ELSE DO SAY '' message ' perdu pour' listpattern.lose.field.i listpattern.lose.field.i.1 = listpattern.lose.field.i.1 + 1 END END return 1 /* keep_rule teaches the program a way to identify good emails */ keep_rule: listerror = 0 CALL learn_rule arg(1), keep IF listerror THEN DO sigh = 'ERREUR : Règle "' arg(1) '" dans SpamFryer.rexx ignorée.' SAY sigh IF logFile ~= '' & verbose THEN CALL WRITELN(log, sigh) END return 1 /* lose_rule teaches the program a way to identify bad emails */ lose_rule: listerror = 0 CALL learn_rule arg(1), lose IF listerror THEN DO sigh = 'ERREUR : Règle "' arg(1) '" dans SpamFryer.rexx ignorée.' SAY sigh IF logFile ~= '' & verbose THEN CALL WRITELN(log, sigh) END return 1 /* learn_rule teaches the program a way to categorise emails. */ /* The first argument is the rule. The second is the category */ /* listerror is set if the rule is not correctly formatted. */ learn_rule: instring = UPPER(arg(1)) kind = arg(2) PARSE VAR instring word1 word2 . /* get the first and the second word in the line */ IF word1 = "//" THEN return 1 /* Allow C++ comments in the configuration lines */ IF word2 = '' THEN DO IF word1 ~= '' THEN listerror = 1 /* Error if there is only one word in the string */ END ELSE DO field = 0 IF RIGHT(word1,1) ~= ':' THEN word1 = word1 || ':' IF word1 = 'TO:' THEN field = gTo IF word1 = 'FROM:' THEN field = gFrom IF word1 = 'REPLY-TO:' THEN field = gReply IF word1 = 'REPLYTO:' THEN field = gReply IF word1 = 'SUBJECT:' THEN field = gSubject IF word1 = 'EARLY-REF:' THEN field = gEarly IF word1 = 'EARLYREF:' THEN field = gEarly IF field = 0 THEN listerror = 1 /* Error, unexpected first word on line */ ELSE DO instring = STRIP(instring,'L') instring = RIGHT(instring,LENGTH(instring)-POS(' ',instring)) /* cut off field parameter, leaving only the search pattern */ instring = STRIP(instring) IF RIGHT(instring,1) = "'" & LEFT(instring,1)="'" THEN instring = STRIP(instring,'B',"'") IF RIGHT(instring,1) = '"' & LEFT(instring,1)='"' THEN instring = STRIP(instring,'B','"') listfield.kind.field = listfield.kind.field + 1 /* count entries in each field parameter for each list */ counter = listfield.kind.field listpattern.kind.field.counter = instring /* Store search pattern */ listpattern.kind.field.counter.1 = 0 /* No matches yet */ IF verbose THEN DO IF kind = keep THEN sigh = 'KEEP' ELSE sigh = 'LOSE' SAY sigh counter word1 instring END END END return 1 /* check_accounts reads POP3 account login details from a file */ Check_Accounts: IF OPEN(list,accountFile,'R') THEN DO accounts = 1 DO WHILE Account.accounts.1 ~='' accounts = accounts + 1 /* Skip acounts defined in this file */ END userSet = '' hostSet = '' passSet = '' counter = count.type DO WHILE ~EOF(list) instring = READLN(list) PARSE VAR instring word1 word2 . /* get the first and the second word in the line */ IF word1 ~= "//" & word1 ~= '' THEN DO word1 = UPPER(word1) field = 0 IF RIGHT(word1,1) ~= ':' THEN word1 = word1 || ':' IF word1 = 'USERNAME:' THEN field = 2 IF word1 = 'PASSWORD:' THEN field = 3 IF word1 = 'HOSTNAME:' THEN field = 1 IF field = 0 THEN DO moan = 'ERREUR : élément non reconnu :' word1 'dans' accountFile say moan IF logFile ~='' THEN CALL WRITELN(log, moan) END IF RIGHT(word2,1) = "'" & LEFT(word2,1)="'" THEN word2 = STRIP(word2,'B',"'") IF RIGHT(word2,1) = '"' & LEFT(word2,1)='"' THEN word2 = STRIP(word2,'B','"') /* Make sure each host has one associated username and one or zero passwords */ ELSE DO IF field = 1 THEN DO IF hostSet ~= '' THEN DO IF userSet = '' THEN DO sigh = 'ERREUR : pas de nom d''utilisateur configuré pour l''hôte' hostSet 'dans' AccountFile SAY sigh IF logFile ~= '' THEN CALL WRITELN(log, sigh) END ELSE DO accounts = accounts + 1 passSet = '' userSet = '' END END hostSet = word2 END IF field = 2 THEN DO IF userSet ~= '' THEN DO IF hostSet = '' THEN DO sigh = 'ERREUR : pas d''hôte configuré pour l''utilisateur' userSet 'dans' AccountFile SAY sigh IF logFile ~= '' THEN CALL WRITELN(log, sigh) END ELSE DO accounts = accounts + 1 hostSet = '' passSet = '' END END userSet = word2 END IF field = 3 THEN DO IF passSet ~= '' THEN DO sigh = 'ERREUR : mots de passes doublés' passSet 'et' word2 'dans' AccountFile SAY sigh IF logFile ~= '' THEN CALL WRITELN(log, sigh) END passSet = word2 END Account.accounts.field = word2 END /* Valid field */ END /* Not comment or blank line */ END /* WHILE list */ CALL CLOSE(list) IF userSet = '' & hostSet = '' THEN DO IF passSet ~= '' THEN DO sigh = 'ERREUR : mot de passe' passSet 'dans' AccountFile 'a besoin d''un hôte et d un nom d''utilisateur' SAY sigh IF logFile ~= '' THEN CALL WRITELN(log, sigh) END END ELSE DO IF hostSet = '' THEN DO sigh = 'ERREUR : pas d''hôte configuré pour l''utilisateur ' userSet 'dans' AccountFile SAY sigh IF logFile ~= '' THEN CALL WRITELN(log, sigh) END IF userSet = '' THEN DO sigh = 'ERREUR : pas d''utilisateur défini pour l''hôte ' hostSet 'dans' AccountFile SAY sigh IF logFile ~= '' THEN CALL WRITELN(log, sigh) END END END IF verbose THEN DO accounts = 1 DO WHILE Account.accounts.1 ~='' sigh = 'Host' Account.accounts.1 'User' Account.accounts.2 'Password' Account.accounts.3 SAY sigh IF verbose THEN CALL writeln(log, sigh) accounts = accounts + 1 END END return 1 /* test_str tests the response from the POP server: +OK or -ERR */ test_str: test = arg(1) /* SAY 'Server Response:' test */ IF POS('+OK',test)~=0 THEN return 1 ELSE moan = 'Login' username || ':' test SAY moan IF logFile ~='' THEN DO IF verbose THEN CALL writeln(log, moan) CALL close(log) END CALL writeln(net,'QUIT'||'0d'x) CALL close(net) EXIT 10